//
// FileUpdater
//
// Copyright (C) jextra.net.
//
//  This file is part of the Crack build system.
//
//  The Crack build system is free software; you can redistribute it 
//  and/or modify it under the terms of the GNU Lesser General Public
//  License as published by the Free Software Foundation; either
//  version 2.1 of the License, or (at your option) any later version.
//
//  The Crack build system is distributed in the hope that it will be 
//  useful, but WITHOUT ANY WARRANTY; without even the implied warranty of
//  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
//  Lesser General Public License for more details.
//
//  You should have received a copy of the GNU Lesser General Public
//  License along with the Crack build system; if not, write to the Free
//  Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
//  02111-1307 USA.
//

package net.jextra.crack.updater;

import java.io.*;
import java.net.*;
import java.util.*;
import net.jextra.crack.*;
import net.jextra.crack.tool.*;

/**
 * Mounted drive, File-based Crack Updater.
 */
public class FileUpdater implements CrackUpdater
{
    // ============================================================
    // Fields
    // ============================================================

    public static final int BUFFER_SIZE = 50000;

    private String name;
    private File rootDir;

    // ============================================================
    // Constructors
    // ============================================================

    public FileUpdater( File rootDir )
        throws IOException
    {
        this( rootDir.getCanonicalPath(), rootDir );
    }

    public FileUpdater( String name, File rootDir )
    {
        setRootDir( rootDir );
    }

    /**
     * Should be used only by a UpdaterFactory.
     */
    protected FileUpdater()
    {
    }

    // ============================================================
    // Methods
    // ============================================================

    // ----------
    // public
    // ----------

    @Override
    public void init( CrackUpdaterParams params )
        throws ClassNotFoundException, InstantiationException, IllegalAccessException
    {
        name = params.getAttribute( "name" );
        setRootDir( new File( params.getAttribute( "dir" ) ) );
        if ( name == null )
        {
            name = rootDir.getAbsolutePath();
        }
    }

    @Override
    public String getName()
    {
        return name;
    }

    @Override
    public int getTimeout()
    {
        return 0;
    }

    public File getRootDir()
        throws IOException
    {
        assert rootDir != null : "rootDir parameter can not be null.";
        assert rootDir.exists() : String.format( "rootDir [%s] does not exist.",
            rootDir.getCanonicalPath() );
        assert rootDir.isDirectory() : String
            .format( "rootDir [%s] must be a directory.",
            rootDir.getCanonicalPath() );

        return rootDir;
    }

    public void setRootDir( File rootDir )
    {
        if ( rootDir == null || !rootDir.exists() || !rootDir.isDirectory() )
        {
            throw new CrackException( "CrackUpdater's root directory '" +
                rootDir.getAbsolutePath() + "' does not exist." );
        }

        this.rootDir = rootDir;
    }

    @Override
    public URL getRootURL()
        throws MalformedURLException
    {
        return rootDir.toURI().toURL();
    }

    @Override
    public List<CrackUpdaterEntry> getEntries( String path )
        throws Exception
    {
        HashSet<File> files = new HashSet<File>();
        path = path == null ? "" : path;
        File updaterDir = new File( rootDir, path );
        files.addAll( searchDir( updaterDir ) );

        ArrayList<CrackUpdaterEntry> entries = new ArrayList<CrackUpdaterEntry>();
        for ( File file : files )
        {
            CrackUpdaterEntry entry = new CrackUpdaterEntry( FileTool.getRelativePath( rootDir,
                file.getCanonicalFile() ), file.isDirectory() ? CrackUpdaterEntry.Type.directory :
                CrackUpdaterEntry.Type.file, file.lastModified(), file.length(),
                CrackUpdaterEntry.getSHA256Hash( file ) );
            entries.add( entry );
        }

        Collections.sort( entries );

        return entries;
    }

    @Override
    public CrackUpdaterEntry getEntry( String path )
        throws Exception
    {
        File file = new File( rootDir, path );
        assert file.exists() : String.format( "entry %s does not exist.", path );

        CrackUpdaterEntry entry = new CrackUpdaterEntry( FileTool.getRelativePath( rootDir,
            file.getCanonicalFile() ), file.isDirectory() ? CrackUpdaterEntry.Type.directory :
            CrackUpdaterEntry.Type.file, file.lastModified(), file.length(),
            CrackUpdaterEntry.getSHA256Hash( file ) );

        return entry;
    }

    @Override
    public URL getFileURL( String path )
        throws Exception
    {
        File file = getFile( path );
        if ( file == null )
        {
            return null;
        }
        return file.getCanonicalFile().toURI().toURL();
    }

    @Override
    public void uploadFile( String path, File file )
        throws Exception
    {
        assert path != null : "path parameter can not be null.";

        copyFile( file, new File( getRootDir(), path ) );
    }

    public File getFile( String path )
        throws IOException
    {
        File file = new File( rootDir, path );
        if ( file.isDirectory() )
        {
            return null;
        }
        return file.getCanonicalFile();
    }

    // ----------
    // private
    // ----------

    private Collection<File> searchDir( File dir )
        throws IOException
    {
        assert dir.exists() : String.format( "%s does not exist.", dir.getCanonicalPath() );
        assert dir.isDirectory() : String.format( "%s must be a directory.",
            dir.getCanonicalPath() );
        HashSet<File> files = new HashSet<File>();
        for ( File file : dir.listFiles() )
        {
            files.add( file );
        }
        return files;
    }

    private boolean copyFile( File from, File to )
        throws IOException
    {
        assert from != null : "From input stream can not be null.";
        assert to != null : "To file can not be null.";
        assert !to.isDirectory() : "To file can not be directory.";
        to.getParentFile().mkdirs();
        System.out.printf( "Copying file [%s] to [%s].\n", from.getCanonicalPath(),
            to.getCanonicalPath() );
        FileInputStream in = new FileInputStream( from );
        FileOutputStream out = new FileOutputStream( to );
        byte[] buf = new byte[BUFFER_SIZE];
        int i = 0;
        while ( ( i = in.read( buf ) ) != -1 )
        {
            out.write( buf, 0, i );
        }
        in.close();
        out.close();
        return true;
    }
}

